/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"

static void vsfusbh_cdc_free_urb(struct vsfusbh_cdc_t *cdc)
{
	struct vsfusbh_t *usbh = cdc->usbh;
	if (cdc->ctrl_urb != NULL)
		vsfusbh_free_urb(usbh, &cdc->ctrl_urb);
	if (cdc->evt_urb != NULL)
		vsfusbh_free_urb(usbh, &cdc->evt_urb);
	if (cdc->in_urb != NULL)
		vsfusbh_free_urb(usbh, &cdc->in_urb);
	if (cdc->out_urb != NULL)
		vsfusbh_free_urb(usbh, &cdc->out_urb);
}

static struct vsfsm_state_t *
vsfusbh_cdc_urb_evt_handler(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	struct vsfusbh_cdc_t *cdc = (struct vsfusbh_cdc_t *)sm->user_data;
	struct vsfhcd_urb_t *urb;
	bool isin;

	switch (evt)
	{
	case VSFSM_EVT_URB_COMPLETE:
		isin = sm == &cdc->in_sm;
		urb = isin ? cdc->in_urb : cdc->out_urb;
		if ((cdc->cb != NULL) && (cdc->cb->on_io != NULL))
			cdc->cb->on_io(cdc, isin, urb);
		break;
	default:
		break;
	}
	return NULL;
}

static void vsfusbh_cdc_on_event(struct vsfusbh_cdc_t *cdc)
{
	if ((cdc->cb != NULL) && (cdc->cb->on_event != NULL))
		cdc->cb->on_event(cdc);
}

vsf_err_t vsfusbh_cdc_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfusbh_cdc_t *cdc = (struct vsfusbh_cdc_t *)pt->user_data;
	struct vsfusbh_t *usbh = cdc->usbh;
	vsf_err_t err;

	vsfsm_pt_begin(pt);

	cdc->in_sm.init_state.evt_handler = vsfusbh_cdc_urb_evt_handler;
	cdc->in_sm.user_data = cdc;
	vsfsm_init(&cdc->in_sm);
	cdc->out_sm.init_state.evt_handler = vsfusbh_cdc_urb_evt_handler;
	cdc->out_sm.user_data = cdc;
	vsfsm_init(&cdc->out_sm);

	if ((cdc->evt_urb != NULL) && (cdc->evtbuf.size > 0))
	{
		cdc->evt_urb->transfer_buffer = cdc->evtbuf.buffer;
		cdc->evt_urb->transfer_length = cdc->evtbuf.size;
		err = vsfusbh_submit_urb(usbh, cdc->evt_urb);
		if (err) goto ret_error;
		vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
		if (cdc->evt_urb->status != URB_OK) goto ret_failure;
		vsfusbh_cdc_on_event(cdc);

		while (1)
		{
			err = vsfusbh_relink_urb(usbh, cdc->evt_urb);
			if (err) goto ret_error;
			vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
			if (cdc->evt_urb->status != URB_OK) goto ret_failure;
			vsfusbh_cdc_on_event(cdc);
		}
	}

	vsfsm_pt_end(pt);
	return VSFERR_NONE;

ret_failure:
	err = VSFERR_FAIL;
ret_error:
	vsfusbh_remove_interface(cdc->usbh, cdc->dev, cdc->ifs);
	return err;
}

vsf_err_t vsfusbh_cdc_init(struct vsfusbh_cdc_t *cdc, struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, struct vsfusbh_ifs_t *ifs)
{
	struct vsfusbh_ifs_alt_t *alt = &ifs->alt[ifs->cur_alt];
	struct usb_interface_desc_t *ifs_desc = alt->ifs_desc;
	struct usb_endpoint_desc_t *ep_desc = alt->ep_desc;
	struct usb_class_interface_descriptor_t *desc;
	uint8_t epaddr, eptype;
	uint8_t parsed_size;

	cdc->usbh = usbh;
	cdc->dev = dev;
	cdc->ifs = ifs;

	cdc->ctrl_urb = vsfusbh_alloc_urb(usbh);
	cdc->in_urb = vsfusbh_alloc_urb(usbh);
	cdc->out_urb = vsfusbh_alloc_urb(usbh);
	if (!cdc->ctrl_urb || !cdc->in_urb || !cdc->out_urb)
		goto free_all;
	cdc->ctrl_urb->hcddev = &dev->hcddev;
	cdc->ctrl_urb->notifier_sm = &cdc->sm;
	cdc->ctrl_urb->pipe = usb_rcvctrlpipe(&dev->hcddev, 0);
	cdc->in_urb->hcddev = &dev->hcddev;
	cdc->in_urb->notifier_sm = &cdc->in_sm;
	cdc->out_urb->hcddev = &dev->hcddev;
	cdc->out_urb->notifier_sm = &cdc->out_sm;
	cdc->out_urb->transfer_flags = URB_ZERO_PACKET;

	// parse control interface
	cdc->comm_iface = ifs_desc->bInterfaceNumber;
	if (ifs_desc->bNumEndpoints == 1)
	{
		epaddr = ep_desc->bEndpointAddress;
		eptype = ep_desc->bmAttributes;
		if ((USB_ENDPOINT_XFER_INT == eptype) && (epaddr & USB_ENDPOINT_DIR_MASK))
		{
			cdc->evt_urb = vsfusbh_alloc_urb(usbh);
			if (!cdc->evt_urb) goto free_all;
			cdc->evt_urb->hcddev = &dev->hcddev;
			cdc->evt_urb->notifier_sm = &cdc->sm;
			cdc->evt_urb->pipe = usb_rcvintpipe(&dev->hcddev, epaddr & 0xF);
		}
	}

	desc = (struct usb_class_interface_descriptor_t *)ifs_desc;
	for (parsed_size = 0; parsed_size < alt->desc_size;)
	{
		if (desc->bDescriptorType == USB_DT_CS_INTERFACE)
		{
			switch (desc->bDescriptorSubType)
			{
			case 0x06:		// Union Functional Descriptor
				{
					struct usb_cdc_union_descriptor_t *union_desc =
						(struct usb_cdc_union_descriptor_t *)desc;
					if (union_desc->bControlInterface != cdc->comm_iface)
						goto free_all;
					cdc->data_iface = union_desc->bSubordinateInterface[0];
				}
				break;
			default:
				if ((cdc->cb != NULL) && (cdc->cb->on_desc != NULL) &&
					cdc->cb->on_desc(cdc, desc))
				{
					goto free_all;
				}
				break;
			}
		}
		parsed_size += desc->bLength;
		desc = (struct usb_class_interface_descriptor_t *)((uint32_t)desc + desc->bLength);
	}

	// parse data interface
	ifs = &dev->config.ifs[cdc->data_iface];
	alt = ifs->alt;
	for (int i = 0; i < ifs->num_of_alt; i++, alt++)
	{
		ifs_desc = alt->ifs_desc;
		ep_desc = alt->ep_desc;
		if ((ifs_desc->bDescriptorType == USB_DT_INTERFACE) &&
			(ifs_desc->bInterfaceClass == USB_CLASS_CDC_DATA) &&
			(ifs_desc->bInterfaceSubClass == 0) &&
			(ifs_desc->bInterfaceProtocol == 0))
		{
			if (ifs_desc->bNumEndpoints == 0)
				continue;

			for (int j = 0; j < ifs_desc->bNumEndpoints; j++)
			{
				epaddr = ep_desc->bEndpointAddress;
				eptype = ep_desc->bmAttributes;
				if (eptype != USB_ENDPOINT_XFER_BULK)
					goto free_all;
				if (epaddr & USB_ENDPOINT_DIR_MASK)
					cdc->in_urb->pipe = usb_rcvbulkpipe(&dev->hcddev, epaddr & 0xF);
				else
					cdc->out_urb->pipe = usb_sndbulkpipe(&dev->hcddev, epaddr & 0xF);

				ep_desc = (struct usb_endpoint_desc_t *)((uint32_t)ep_desc + ep_desc->bLength);
			}
			break;
		}
	}

	return VSFERR_NONE;

free_all:
	vsfusbh_cdc_free_urb(cdc);
	return VSFERR_FAIL;
}

void vsfusbh_cdc_fini(struct vsfusbh_cdc_t *cdc)
{
	vsfusbh_cdc_free_urb(cdc);
	vsfsm_fini(&cdc->in_sm);
	vsfsm_fini(&cdc->out_sm);
	vsfsm_fini(&cdc->sm);
}
